use Net::Ping;
use Interface;
use Device;
use JSON;

use strict;

package Discover;

my $sysName = "1.3.6.1.2.1.1.5.0";
my $sysDescr = "1.3.6.1.2.1.1.1.0";
my $sysLocation = "1.3.6.1.2.1.1.6.0";
my $sysObjectId = "1.3.6.1.2.1.1.2.0";

my $icmp = new Net::Ping;

sub new
{
    my $class = shift;
    my $this = {
	devices => [],
	vpns => {},
	defaultVPN => 0,
    };
    bless $this, $class;
}

sub vpn {
    my ($this) = @_;
    foreach my $dev (@{$this->{devices}}){
	while ( my ($key, $value) = each %{$$dev->{vpns}} ){
	    if(! defined ($this->{vpns}->{$key})){
		$this->{vpns}->{$key} = $value;
	    }
	}
    }
}

sub discover {
    my ($this, $net, @comms) = @_;
    my $last = $net->last_int();
    for(;defined $net && $net->intip() <= $last; $net++){
	    if($icmp->ping($net->ip, 0.2)){
	    foreach my $comm (@comms){
		print "$comm\@" . $net->ip . " : ";
		my $sysId = ::get($net->ip, $comm, $sysObjectId);
		if(defined $sysId && $sysId != 0){
		    print "\n";
		    my $d = new Device($net->ip, $comm, $sysId);
		    $d->{defaultVPN} = $this->{defaultVPN};
		    $d->discover($this->{doMTable});
		    $d->print;
		    push(@{$this->{devices}}, \$d);
		    last;
		} else { print "\n"; }
	    }
	} else {
	}
    }
    $this->vpn;
    print "Discovered " . scalar(@{$this->{devices}}) . " devices!\n";
}

my ($DBHOST, $DBUSER, $DBPASS, $SCHEMA) = ("localhost", "root", "mysql", "pnetdoc");

use DBI;
sub save {
    my ($this) = @_;
    my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
    #
    # First saving vpn info
    #
    my $vpnsql = "insert into vpn values (?, ?, ?)";
#    print ::Dumper($this->{vpns});
    $dbh->do($vpnsql, undef, 0, "GLOBAL", 10);
    while ( my ($key, $value) = each %{$this->{vpns}} ){
	if($key ne '0'){
	    $dbh->do($vpnsql, undef, $key, %$value->{name}, %$value->{type});
	}
    }
    #
    # Next saving device info
    #
    my $devsql = "insert into device values (?, ?, ?, ?, ?, ?, ?)";
    my $intsql = "insert into interface values (?, ?, ?, ?, ?)";
    my $ipsql = "insert into ips values (?, ?, ?, ?, ?)";
    foreach my $dev (@{$this->{devices}}){
	$dbh->do($devsql, undef, $$dev->{name}, $$dev->{ip}, $$dev->{community}, $$dev->{sysid}, $$dev->{descr}, $$dev->{location}, $$dev->{id});
	#
	# Saving device interface info
	#
	while ( my ($ikey, $ivalue) = each %{$$dev->{interfaces}} ){
	    $dbh->do($intsql, undef, $ikey, $$dev->{id}, $ivalue->{ifname}, $ivalue->{ifalais}, $ivalue->{vpn});
	    #
	    # Saving IP addresses
	    #
	    foreach my $ip (@{$ivalue->{ips}}){
		$dbh->do($ipsql, undef, $ip->addr(), $ip->masklen(), $ivalue->{vpn}, $ikey, $$dev->{id})
		or
		printf "[ERROR] Cant save ip %s, %s, %s, %s, %s, %s, %s\n", $ip->addr, $ip->masklen, $ivalue->{vpn}, $ikey, $ivalue->{ifname}, $ivalue->{ifalias}, $$dev->{id};
	    }
	}
	my $bsql = "insert into bgp values (?, ?, ?, ?)";
	while( my ($nip, $ras) = each(%{$$dev->{bgpPeers}}) ){
	    $dbh->do($bsql, undef, $ras->{local}, $nip, $$dev->{id}, $ras->{ras});
	}
	
	my $asql = "insert into arp values (?, ?, ?, ?)";
	foreach my $arp (@{$$dev->{arp}}){
	    $dbh->do($asql, undef, $$dev->{id}, $arp->{ifindex}, $arp->{mac}, $arp->{ip});
	}
	
	my $msql = "insert into mtable values (?, ?, ?)";
	foreach my $mt (@{$$dev->{macTable}}){
	    $dbh->do($msql, undef, $$dev->{id}, $mt->{ifindex}, $mt->{mac});
	}
    }
}

sub clean(){
    my ($this) = @_;
    my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
    my $vpnsql = "delete from vpn where id=?";
    $dbh->do($vpnsql, undef, 0);
    while ( my ($key, $value) = each %{$this->{vpns}} ){
	$dbh->do($vpnsql, undef, $key);
    }
#    my $devsql = "delete from device where id=?";
    my $devsql = "delete from device where ip=?";
#    my $intsql = "delete from interface where id=? and device=?";
    my $intsql = "delete from interface where device=?";
#    my $ipsql = "delete from ips where ip=? and device=? and vpn=?";
    my $ipsql = "delete from ips where device=?";
    my $bsql = "delete from bgp where device=?";
    my $asql = "delete from arp where device=?";
    my $msql = "delete from mtable where device=?";
    foreach my $dev (@{$this->{devices}}){
	$dbh->do($devsql, undef, $$dev->{ip});
	$dbh->do($intsql, undef, $$dev->{id});
	$dbh->do($ipsql, undef, $$dev->{id});
	$dbh->do($bsql, undef, $$dev->{id});
	$dbh->do($asql, undef, $$dev->{id});
	$dbh->do($msql, undef, $$dev->{id});
#	while ( my ($ikey, $ivalue) = each %{$$dev->{interfaces}} ){
#	    $dbh->do($intsql, undef, $ikey, $$dev->{id});
#	    foreach my $ip (@{$ivalue->{ips}}){
#		$dbh->do($ipsql, undef, $ip->addr(), $$dev->{id}, $ivalue->{vpn});
#	    }
#	}
#	my $bsql = "delete from bgp where ip=? and nip=? and ras=?";
#	while( my ($nip, $bgp) = each(%{$$dev->{bgpPeers}}) ){
#	    $dbh->do($bsql, undef, $bgp->{local}, $nip, $bgp->{ras});
#	}
    }
    

}

sub wcheck {
    my ($this, $sql, $message) = @_;
    my ($first, $second, $third);
    my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
    my $sth = $dbh->prepare($sql);
    $sth->execute();
    $sth->bind_columns(undef, \$first, \$second, \$third);
    my $res = [];
    while($sth->fetch()) { 
	my $m = sprintf ($message, $first, $second, $third); 
	my $row = {type=>"WARNING", message=>$m};
	push(@{$res}, $row);
    }
    return $res;
}

sub wreport
{
    my ($this, $iscope_regexp, $ialias_regexp) = @_;
    my $res = ();
    $res->{operation} = "SCAN";
    $res->{result}->{format} = "table";
    $res->{result}->{headers} = {type => "Type", message => "Message"};
    my $m1 = $this->wcheck("select name, ip, '' from device where name = ''", "Empty device name : %s/(%s)");
    my $m2 = $this->wcheck("select name, ip, '' from device where descr = ''", "Empty device description : %s/(%s)");
    my $m3 = $this->wcheck("select name, ip, '' from device where location = ''", "Empty device location : %s(%s)");
    my $m4 = $this->wcheck("select i.name, CONCAT(d.name, '|', d.ip), alias from interface i, device d where d.id = i.device and alias not rlike '$ialias_regexp' and i.name not rlike '$iscope_regexp'", "Invalid interface description : %s@%s - '%s'");
    my @m5 = (@$m1, @$m2, @$m3, @$m4);
    $res->{result}->{data} = \@m5;
    return [$res];
}

sub domain
{
    my ($this, $zone, $ifregexp, $scope, $header, $reverse) = @_;
    my ($ip, $device, $dip, $ifname, $rzone, $origin);
    if($reverse){
	$origin = $scope;
        $origin =~ s/([0-9]+).([0-9]+).([0-9]+).([0-9]+)\/([0-9]+)/$4\/$5.$3.$2.$1/;
        $origin =~ s/^.+\/24\.//;
    } else {
	$origin = $zone;
    }
    if($header){
	print "\$TTL 2d  ; 172800 seconds
\$ORIGIN $origin.
@\t\tIN\tSOA\tns1.$zone. hostmaster.$zone. (
\t\t\t\t2003080800 ; serial number
\t\t\t\t3h         ; refresh
\t\t\t\t15m        ; update retry
\t\t\t\t3w         ; expiry
\t\t\t\t3h         ; minimum
\t\t\t)
\t\tIN      NS      ns1.$zone.
\t\tIN      NS      ns2.$zone.\n\n";
    }
    
    
    my $sql = "select ip.ip, d.name, d.ip, i.name from ips ip, interface i, device d where i.device=ip.device and i.id=ip.ifindex and ip.device = d.id";
    my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
    my $sth = $dbh->prepare($sql);
    $sth->execute();
    $sth->bind_columns(undef, \$ip, \$device, \$dip, \$ifname);
    while($sth->fetch()) { 
	if($scope->contains(new NetAddr::IP("$ip/32"))){
	    $ifname =~ s/$ifregexp/-/g;
	    $device =~ s/[\s\/\.\:]/-/g;
	    if($ip eq $dip){
		printf (($reverse)?"$ip\t\tIN\tA\t$device.$zone.\n":"$device\t\tIN\tA\t$ip\n"); 
	    }
	    printf (($reverse)?"$ip\t\tIN\tA\t$ifname.$device.$zone.\n":"$ifname.$device\t\tIN\tA\t$ip\n"); 
	}
    } 
}

sub info{
    my ($this, $ip, $exact, $showarp) = @_;
    my ($name, $descr, $location, $did, $dip, $iid, $iname, $ialias, $ipip, $ipmask, $vname);
    my $dsql = "select name, descr, location, ip from device where ip=?";
    my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
    my $sth = $dbh->prepare($dsql);

    $sth->execute($ip);
    $sth->bind_columns(undef, \$name, \$descr, \$location, \$dip);
    while($sth->fetch()) { 
	printf "---------- Device: %s ------------\nDescription: %s\nLocation: %s\nIp: %s\n\n", $name, $descr, $location, $ip;
    }

    my $isql = "select d.name, d.descr, d.location, d.id, d.ip, i.id, i.name, i.alias, ip.ip, ip.mask, v.name from vpn v, device d, interface i, ips ip where ip.ifindex=i.id and ip.device=i.device and i.device=d.id and i.vpn=v.id";
    my $sth = $dbh->prepare($isql);

    $sth->execute();
    $sth->bind_columns(undef, \$name, \$descr, \$location, \$did, \$dip, \$iid, \$iname, \$ialias, \$ipip, \$ipmask, \$vname);
    my $iph = new NetAddr::IP($ip);
    while($sth->fetch()) { 
	my $net = new NetAddr::IP("$ipip/$ipmask");
	if(($exact && ($ip eq $ipip)) || (!$exact && $net->contains($iph))){
	    printf "----------- Device: %s (%s) -------------\nDescription: %s\nLocation: %s\nInterface: %s(%s) : %s\nIp: %s/%s\nVpn: %s\n", $name, $dip, $descr, $location, $iname, $iid, $ialias, $ipip, $ipmask, $vname;
	    if($showarp){
		my $asql = "select mac, ip from arp where device=? and ifindex=? order by mac asc";
		my $asth = $dbh->prepare($asql);
	        my ($mac, $ip);
		$asth->execute($did, $iid);
		$asth->bind_columns(undef, \$mac, \$ip);
		printf "ARP entries:\n";
		while($asth->fetch){
		    printf "%s : %s\n", $mac, $ip;
		
		    my $msql = "select d.name, i.name, i.alias from mtable m, device d, interface i where m.mac=? and d.id = m.device and i.id = m.ifindex and i.device=m.device";
		    my $msth = $dbh->prepare($msql);
		    my ($edid, $eii, $ealias);
		    $msth->execute($mac);
		    $msth->bind_columns(undef, \$edid, \$eii, \$ealias);
		    while($msth->fetch){
			printf "Egress port : %s@%s (%s)\n", $eii, $edid, $ealias;
		    }
		}
	    }
	}
    }
}

sub whois
{
    my ($whoissrv, $request, $whoisfield, $regexp) = @_;
    my @output = `whois -h $whoissrv $request`;
    my ($name, $value, $asname);
    foreach (@output) {
        if (/No entries found/i){
		last;
        }
        if (/$whoisfield:/){
	    (undef, $asname) = split /:\s+|\n|\s+#/, $_;
	}
    }
    return $asname;
}

sub bgp
{
    my ($this, $exclude, $include, $detail) = @_;
    my ($local, $remote, $ras, $dip, $dname);
    my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
    my $sql = "select b.ip, b.nip, b.ras, d.ip, d.name from bgp b, device d where d.id=b.device and ras not rlike ? and ras rlike ?";
    my $sth = $dbh->prepare($sql);
    $sth->execute($exclude, $include);
    $sth->bind_columns(undef, \$local, \$remote, \$ras, \$dip, \$dname);
    my $peers = ();
    while($sth->fetch()) { 
	my $pname = whois("whois.ripe.net", "AS$ras", "as-name");
	push(@{$peers}, {device => $dname, device_ip => $dip, local_ip => $local, remote_ip => $remote, ras => $ras, pname => $pname});
	if($detail){
	    $this->info($remote, 0);
	}
    }
    my $headers = [{device => "Device"}, {device_ip => "Device IP"}, {local_ip => "Local IP"}, {remote_ip => "Remote IP"}, {ras => "Remote AS"}, {pname => "Peer name"}];

    my $res = [];
    push(@$res, {operation => "BGP", result => {format=>"table", headers => $headers, data => $peers}});
    return $res;
}

sub vrf{
    my ($this, $name) = @_;
    if(defined $name){
	my ($dev, $location, $if, $alias);
        my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
	my $sql = "select d.name as dname, d.location, i.name, i.alias from vpn v, device d, interface i where i.vpn=v.id and i.device = d.id and v.name rlike '$name' order by dname";
        my $sth = $dbh->prepare($sql);
	$sth->execute();
        $sth->bind_columns(undef, \$dev, \$location, \$if, \$alias);
	my $pdev;
        while($sth->fetch()) { 
    	    if($pdev ne $dev){
		printf "\n----- %s -----\nLocation: %s\n", $dev, $location;
		$pdev = $dev;
	    }
	    printf "%s:%s\n", $if, $alias;
	}
    } else {
	my ($id, $name);
	my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
	my $sql = "select v.id, v.name from vpn v order by name";
	my $sth = $dbh->prepare($sql);
	$sth->execute();
	$sth->bind_columns(undef, \$id, \$name);
	while($sth->fetch()) { 
	    printf "%s:%s\n", $name, $id;
	}
    }
}

sub usage {
    my ($this, $net, $free) = @_;
    my ($ip, $mask, $device, $dip, $ifindex, $ifname, $ifalias);
    my $dbh = DBI->connect("DBI:mysql:$SCHEMA", $DBUSER, $DBPASS) || die "Could not connect to database: $DBI::errstr";
    my $sql = "select ip.ip, ip.mask, d.name, d.ip, i.id, i.name, i.alias alias from ips ip, interface i, device d where i.device=ip.device and i.id = ip.ifindex and d.id = i.device and inet6_mask(inet6_pton(ip.ip), " . $net->masklen() . ") = inet6_mask(inet6_pton('" . $net->addr() . "'), " . $net->masklen() . ") order by inet6_pton(ip.ip);";
    my $sth = $dbh->prepare($sql);
    $sth->execute();
    $sth->bind_columns(undef, \$ip, \$mask, \$device, \$dip, \$ifindex, \$ifname, \$ifalias);
    my $pip = new NetAddr::IP($net->addr());
    my $complete = 0;
    my $subnets = ();
    while(!$complete){
	my $sip;
	if($sth->fetch()){
	    $sip = new NetAddr::IP($ip . "/" . $mask);
	} else {
	    $complete = 1;
	    $sip = new NetAddr::IP($net->broadcast()->numeric() + 1);
	}
	if($free){
	    my $fs = $pip->broadcast()->numeric() + 1;
	    while ($fs < $sip->network()->numeric()){
		my $fm = 0;
		for(my $i = 32;; $i--){
		    if(($fs % (1<<$i)) == 0){
			$fm = 32 - $i;
			last;
		    }
		}
		my $fip = new NetAddr::IP($fs, $fm);
		my $ripe_netname = whois("whois.ripe.net", $fip->addr(), "netname");
		push(@{$subnets}, {net => $fip->addr() . "/" . $fm, dip => undef, ifindex => undef, descr => "FREE SUBNET", ip => undef, netname => $ripe_netname});
		$fs += (1<<(32 - $fm));
	    }
	}
	if($complete) { last; }
	if($free < 2){
	    my $ripe_netname = whois("whois.ripe.net", $ip, "netname");
	    my $ipnet = new NetAddr::IP($ip, $mask);
	    push(@{$subnets}, {net => $ipnet->network(), dip => $device, ifindex => $ifindex, descr => sprintf ( "%s@%s [%s]", $ifname, $device, $ifalias), ip => $ip, netname => $ripe_netname});
	}
	$pip = $sip->broadcast();
    }
    my $headers = [{net => "Net"}, {dip => "Device"}, {ifindex => "Interface"}, {descr => "Description"}, {ip => "Interface IP"}, {netname => "Registry Netname"}];

    my $res = [];
    push(@$res, {operation => "IP Usage", result => {format=>"table", headers => $headers, data => $subnets}});
    return $res;
}

sub print_operation {
    my ($op) = @_;
    my $title = $op->{operation};
    my $headers = $op->{result}->{headers};
    my $data = $op->{result}->{data};
    my $format = $op->{result}->{format};
    printf "[%s]\n", $title;
    if($format eq "table"){
	foreach my $hrow (@{$headers}){
	    my ($key, $value) = each(%{$hrow});
	    printf "%s:\t", $value;
	}
	printf "\n";

	foreach my $row (@$data){
	    foreach my $hrow (@{$headers}){
		my ($key) = keys (%{$hrow});
		printf "\"%s\"\t", $row->{$key};
	    }
	    printf "\n";
	}
    } else {
	foreach my $row (@$data){
	    while(my ($key, $value) = each(%{$row})){
		printf "%s:\t%s\n", $headers->{$key}, $value;
	    }
	    printf "\n";
	}
    }
}

sub output {
    my ($res) = @_;
    foreach my $operation (@$res){
	print_operation($operation);
    }
}

sub json {
    my ($res) = @_;
    my $json = JSON->new->allow_nonref;
    print $json->encode($res);
}

1;
